﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pipes.Queries.Model.Pattern;

namespace Pipes.Queries.Parser
{
  public class RegExpParser : BaseParser
  {
    private enum SymbolIndex
    {
      /// <c> (EOF) </c>
      @Eof = 0,
      /// <c> (Error) </c>
      @Error = 1,
      /// <c> '$' </c>
      @Dollar = 2,
      /// <c> '(' </c>
      @Lparen = 3,
      /// <c> ')' </c>
      @Rparen = 4,
      /// <c> '*' </c>
      @Times = 5,
      /// <c> ',' </c>
      @Comma = 6,
      /// <c> '.' </c>
      @Dot = 7,
      /// <c> '?' </c>
      @Question = 8,
      /// <c> '[' </c>
      @Lbracket = 9,
      /// <c> '[^' </c>
      @Lbracketcaret = 10,
      /// <c> ']' </c>
      @Rbracket = 11,
      /// <c> '^' </c>
      @Caret = 12,
      /// <c> '{' </c>
      @Lbrace = 13,
      /// <c> '|' </c>
      @Pipe = 14,
      /// <c> '}' </c>
      @Rbrace = 15,
      /// <c> '+' </c>
      @Plus = 16,
      /// <c> Character </c>
      @Character = 17,
      /// <c> CharClass </c>
      @Charclass = 18,
      /// <c> Escape </c>
      @Escape = 19,
      /// <c> Integer </c>
      @Integer = 20,
      /// <c> NewLine </c>
      @Newline = 21,
      /// <c> SetEscape </c>
      @Setescape = 22,
      /// <c> SetRange </c>
      @Setrange = 23,
      /// <c> &lt;Concat&gt; </c>
      @Concat = 24,
      /// <c> &lt;Greedy Opt&gt; </c>
      @Greedyopt = 25,
      /// <c> &lt;Line Pattern&gt; </c>
      @Linepattern = 26,
      /// <c> &lt;Match&gt; </c>
      @Match = 27,
      /// <c> &lt;Match Repeat&gt; </c>
      @Matchrepeat = 28,
      /// <c> &lt;Reg Exp&gt; </c>
      @Regexp = 29,
      /// <c> &lt;RegEx File&gt; </c>
      @Regexfile = 30,
      /// <c> &lt;Set&gt; </c>
      @Set = 31,
      /// <c> &lt;Set Item&gt; </c>
      @Setitem = 32,
      /// <c> &lt;Set Items&gt; </c>
      @Setitems = 33
    }

    private enum ProductionIndex
    {
      /// <c> &lt;RegEx File&gt; ::= &lt;Reg Exp&gt; NewLine &lt;RegEx File&gt; </c>
      @Regexfile_Newline = 0,
      /// <c> &lt;RegEx File&gt; ::= &lt;Reg Exp&gt; NewLine </c>
      @Regexfile_Newline2 = 1,
      /// <c> &lt;RegEx File&gt; ::= &lt;Reg Exp&gt; </c>
      @Regexfile = 2,
      /// <c> &lt;Reg Exp&gt; ::= &lt;Line Pattern&gt; '|' &lt;Reg Exp&gt; </c>
      @Regexp_Pipe = 3,
      /// <c> &lt;Reg Exp&gt; ::= &lt;Line Pattern&gt; </c>
      @Regexp = 4,
      /// <c> &lt;Line Pattern&gt; ::= '^' &lt;Concat&gt; '$' </c>
      @Linepattern_Caret_Dollar = 5,
      /// <c> &lt;Line Pattern&gt; ::= '^' &lt;Concat&gt; </c>
      @Linepattern_Caret = 6,
      /// <c> &lt;Line Pattern&gt; ::= &lt;Concat&gt; '$' </c>
      @Linepattern_Dollar = 7,
      /// <c> &lt;Line Pattern&gt; ::= &lt;Concat&gt; </c>
      @Linepattern = 8,
      /// <c> &lt;Concat&gt; ::= &lt;Concat&gt; &lt;Match Repeat&gt; </c>
      @Concat = 9,
      /// <c> &lt;Concat&gt; ::= &lt;Match Repeat&gt; </c>
      @Concat2 = 10,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; '*' &lt;Greedy Opt&gt; </c>
      @Matchrepeat_Times = 11,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; '+' &lt;Greedy Opt&gt; </c>
      @Matchrepeat_Plus = 12,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; '?' &lt;Greedy Opt&gt; </c>
      @Matchrepeat_Question = 13,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; '{' Integer '}' &lt;Greedy Opt&gt; </c>
      @Matchrepeat_Lbrace_Integer_Rbrace = 14,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; '{' Integer ',' '}' &lt;Greedy Opt&gt; </c>
      @Matchrepeat_Lbrace_Integer_Comma_Rbrace = 15,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; '{' Integer ',' Integer '}' &lt;Greedy Opt&gt; </c>
      @Matchrepeat_Lbrace_Integer_Comma_Integer_Rbrace = 16,
      /// <c> &lt;Match Repeat&gt; ::= &lt;Match&gt; </c>
      @Matchrepeat = 17,
      /// <c> &lt;Greedy Opt&gt; ::= '?' </c>
      @Greedyopt_Question = 18,
      /// <c> &lt;Greedy Opt&gt; ::=  </c>
      @Greedyopt = 19,
      /// <c> &lt;Match&gt; ::= '(' &lt;Reg Exp&gt; ')' </c>
      @Match_Lparen_Rparen = 20,
      /// <c> &lt;Match&gt; ::= &lt;Set&gt; </c>
      @Match = 21,
      /// <c> &lt;Match&gt; ::= '.' </c>
      @Match_Dot = 22,
      /// <c> &lt;Match&gt; ::= ',' </c>
      @Match_Comma = 23,
      /// <c> &lt;Match&gt; ::= Character </c>
      @Match_Character = 24,
      /// <c> &lt;Match&gt; ::= Integer </c>
      @Match_Integer = 25,
      /// <c> &lt;Match&gt; ::= Escape </c>
      @Match_Escape = 26,
      /// <c> &lt;Match&gt; ::= SetEscape </c>
      @Match_Setescape = 27,
      /// <c> &lt;Match&gt; ::= SetRange </c>
      @Match_Setrange = 28,
      /// <c> &lt;Set&gt; ::= '[' &lt;Set Items&gt; ']' </c>
      @Set_Lbracket_Rbracket = 29,
      /// <c> &lt;Set&gt; ::= '[^' &lt;Set Items&gt; ']' </c>
      @Set_Lbracketcaret_Rbracket = 30,
      /// <c> &lt;Set&gt; ::= '[' CharClass ']' </c>
      @Set_Lbracket_Charclass_Rbracket = 31,
      /// <c> &lt;Set Items&gt; ::= &lt;Set Item&gt; &lt;Set Items&gt; </c>
      @Setitems = 32,
      /// <c> &lt;Set Items&gt; ::= &lt;Set Item&gt; </c>
      @Setitems2 = 33,
      /// <c> &lt;Set Item&gt; ::= Character </c>
      @Setitem_Character = 34,
      /// <c> &lt;Set Item&gt; ::= Integer </c>
      @Setitem_Integer = 35,
      /// <c> &lt;Set Item&gt; ::= SetRange </c>
      @Setitem_Setrange = 36,
      /// <c> &lt;Set Item&gt; ::= SetEscape </c>
      @Setitem_Setescape = 37,
      /// <c> &lt;Set Item&gt; ::= '*' </c>
      @Setitem_Times = 38,
      /// <c> &lt;Set Item&gt; ::= '+' </c>
      @Setitem_Plus = 39,
      /// <c> &lt;Set Item&gt; ::= '?' </c>
      @Setitem_Question = 40,
      /// <c> &lt;Set Item&gt; ::= '(' </c>
      @Setitem_Lparen = 41,
      /// <c> &lt;Set Item&gt; ::= ')' </c>
      @Setitem_Rparen = 42,
      /// <c> &lt;Set Item&gt; ::= '{' </c>
      @Setitem_Lbrace = 43,
      /// <c> &lt;Set Item&gt; ::= '}' </c>
      @Setitem_Rbrace = 44,
      /// <c> &lt;Set Item&gt; ::= '|' </c>
      @Setitem_Pipe = 45,
      /// <c> &lt;Set Item&gt; ::= '$' </c>
      @Setitem_Dollar = 46,
      /// <c> &lt;Set Item&gt; ::= '.' </c>
      @Setitem_Dot = 47,
      /// <c> &lt;Set Item&gt; ::= ',' </c>
      @Setitem_Comma = 48
    }

    public RegExpParser() : base("Pipes.Queries.Grammar.RegExp.egt") { }
    private PatternOpts _patternOpts;
    private PatternSimplifyVisitor _simplify = new PatternSimplifyVisitor();

    public PatternOpts PatternOpts
    {
      get
      {
        return _patternOpts;
      }
    }

    public override bool Parse(System.IO.TextReader reader)
    {
      _patternOpts = null;
      var result = base.Parse(reader);
      if (result)
      {
        _patternOpts = (PatternOpts)_stack.Pop();
        _patternOpts.Visit(_simplify);
      }
      return result;
    }

    protected override void ActOnReduction(GOLD.Reduction reduc)
    {
      char currChar;
      IMatch match;

      switch ((ProductionIndex)reduc.Parent.TableIndex())
      {
        case ProductionIndex.Match_Character:
        case ProductionIndex.Match_Integer:
        case ProductionIndex.Match_Setrange:
          _stack.Push((string)reduc[0].Data);
          break;
        case ProductionIndex.Match_Comma:
          _stack.Push(",");
          break;
        case ProductionIndex.Match_Dot:
          _stack.Push(new CharSet('.'));
          break;
        case ProductionIndex.Match_Setescape:
          currChar = ((string)reduc[0].Data)[1];
          _stack.Push(currChar.ToString());
          break;
        case ProductionIndex.Match_Escape:
          currChar = ((string)reduc[0].Data)[1];
          switch (currChar)
          {
            case 'd':
            case 'D':
            case 'w':
            case 'W':
            case 's':
            case 'S':
              _stack.Push(new CharSet(currChar));
              break;
            case 'A':
            case 'b':
            case 'Z':
            case 'z':
              _stack.Push(new Anchor(currChar));
              break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
              // backreference
              break;
            default:
              _stack.Push(currChar.ToString());
              break;
          }
          break;
        case ProductionIndex.Match_Lparen_Rparen:
          _stack.Push(new Capture() { Opts = (PatternOpts)_stack.Pop() });
          break;

        case ProductionIndex.Setitem_Character:
        case ProductionIndex.Setitem_Comma:
        case ProductionIndex.Setitem_Dollar:
        case ProductionIndex.Setitem_Dot:
        case ProductionIndex.Setitem_Integer:
        case ProductionIndex.Setitem_Lbrace:
        case ProductionIndex.Setitem_Lparen:
        case ProductionIndex.Setitem_Pipe:
        case ProductionIndex.Setitem_Plus:
        case ProductionIndex.Setitem_Question:
        case ProductionIndex.Setitem_Rbrace:
        case ProductionIndex.Setitem_Rparen:
        case ProductionIndex.Setitem_Times:
        case ProductionIndex.Setitem_Setrange:
          _stack.Push((string)reduc[0].Data);
          break;
        case ProductionIndex.Setitem_Setescape:
          _stack.Push(((string)reduc[0].Data).Substring(1));
          break;

        case ProductionIndex.Setitems:
          ConcatSetItems();
          break;
        case ProductionIndex.Setitems2:
          StackToSet();
          break;

        case ProductionIndex.Set_Lbracketcaret_Rbracket:
          ((CharSet)_stack.Peek()).InverseSet = true;
          break;

        case ProductionIndex.Greedyopt_Question:
          _stack.Push(false);
          break;
        case ProductionIndex.Greedyopt:
          _stack.Push(true);
          break;

        case ProductionIndex.Matchrepeat:
          _stack.Push(true);
          _stack.Push(1);
          _stack.Push(1);
          break;
        case ProductionIndex.Matchrepeat_Lbrace_Integer_Comma_Integer_Rbrace:
          _stack.Push(int.Parse((string)reduc[2].Data));
          _stack.Push(int.Parse((string)reduc[3].Data));
          break;
        case ProductionIndex.Matchrepeat_Lbrace_Integer_Comma_Rbrace:
          _stack.Push(int.Parse((string)reduc[2].Data));
          _stack.Push(int.MaxValue);
          break;
        case ProductionIndex.Matchrepeat_Lbrace_Integer_Rbrace:
          _stack.Push(int.Parse((string)reduc[2].Data));
          _stack.Push(int.Parse((string)reduc[2].Data));
          break;
        case ProductionIndex.Matchrepeat_Plus:
          _stack.Push(1);
          _stack.Push(int.MaxValue);
          break;
        case ProductionIndex.Matchrepeat_Question:
          _stack.Push(0);
          _stack.Push(1);
          break;
        case ProductionIndex.Matchrepeat_Times:
          _stack.Push(0);
          _stack.Push(int.MaxValue);
          break;

        case ProductionIndex.Linepattern_Caret:
          ((Pattern)_stack.Peek()).Matches.Insert(0, new Anchor('^'));
          break;
        case ProductionIndex.Linepattern_Caret_Dollar:
          ((Pattern)_stack.Peek()).Matches.Insert(0, new Anchor('^'));
          ((Pattern)_stack.Peek()).Matches.Add(new Anchor('$'));
          break;
        case ProductionIndex.Linepattern_Dollar:
          ((Pattern)_stack.Peek()).Matches.Add(new Anchor('$'));
          break;

        case ProductionIndex.Concat:
          ConcatPattern();
          break;
        case ProductionIndex.Concat2:
          StackToPattern();
          break;

        case ProductionIndex.Regexp:
          StackToPatternOpts();
          break;
        case ProductionIndex.Regexp_Pipe:
          ConcatPatternOpts();
          break;
      }
    }

    private void StackToSet()
    {
      var set = new CharSet();
      AddStringToSet(set, (string)_stack.Pop());
      _stack.Push(set);
    }
    private void AddStringToSet(CharSet set, string value)
    {
      if (value.Length == 3 && value[1] == '-')
      {
        set.AddRange(value[0], value[2]);
      }
      else if (!string.IsNullOrEmpty(value))
      {
        set.Chars.Insert(0, value[0]);
      }
    }
    private void ConcatSetItems()
    {
      var set = (CharSet)_stack.Pop();
      var value = (string)_stack.Pop();
      AddStringToSet(set, value);
      _stack.Push(set);
    }
    private void StackToPattern()
    {
      TakeFromStack(4);
      IMatch match;
      if (_top[0] is string)
      {
        var strMatch = new StringMatch();
        strMatch.Match.Append((string)_top[0]);
        match = strMatch;
      }
      else
      {
        match = _top[0] as IMatch;
      }
      match.Repeat.Greedy = (bool)_top[1];
      match.Repeat.MinCount = (int)_top[2];
      match.Repeat.MaxCount = (int)_top[3];

      var pattern = new Pattern();
      pattern.Matches.Add(match);
      _stack.Push(pattern);
    }
    private void ConcatPattern()
    {
      TakeFromStack(4);

      var pattern = _stack.Peek() as Pattern;
      var lastString = pattern.Matches.Last() as StringMatch;
      var strTop = _top[0] as string;
      IMatch match = _top[0] as IMatch;
      var greedy = (bool)_top[1];
      var minCount = (int)_top[2];
      var maxCount = (int)_top[3];

      if (strTop == null)
      {
        match.Repeat.Greedy = greedy;
        match.Repeat.MaxCount = maxCount;
        match.Repeat.MinCount = minCount;
        pattern.Matches.Add(match);
      }
      else
      {
        if (lastString == null || lastString.Repeat.Greedy != greedy ||
          lastString.Repeat.MaxCount != maxCount || lastString.Repeat.MinCount != minCount)
        {
          var strMatch = new StringMatch();
          strMatch.Match.Append(strTop);
          strMatch.Repeat.Greedy = greedy;
          strMatch.Repeat.MaxCount = maxCount;
          strMatch.Repeat.MinCount = minCount;
          pattern.Matches.Add(strMatch);
        }
        else
        {
          lastString.Match.Append(strTop);
        }
      }
    }
    private void StackToPatternOpts()
    {
      var pattern = _stack.Pop() as Pattern;
      var opts = new PatternOpts();
      opts.Patterns.Add(pattern);
      _stack.Push(opts);
    }
    private void ConcatPatternOpts()
    {
      var opts = _stack.Pop() as PatternOpts;
      var pattern = _stack.Pop() as Pattern;
      opts.Patterns.Insert(0, pattern);
      _stack.Push(opts);
    }

    private void AddStringMatchChar(char currChar)
    {
      GetCreateMatch().Match.Append(currChar);
    }
    private void AddStringMatchChar(string currChar)
    {
      GetCreateMatch().Match.Append(currChar);
    }
    private StringMatch GetCreateMatch()
    {
      var match = _stack.Peek() as StringMatch;
      if (match == null)
      {
        match = new StringMatch();
        _stack.Push(match);
      }
      return match;
    }

    private static string TestParse(string query)
    {
      var parser = new RegExpParser();
      using (var reader = new System.IO.StringReader(query))
      {
        parser.Parse(reader);
      }
      return parser.PatternOpts.ToString();
    }
  }
}